`timescale 1ns/100ps
`default_nettype none

`define FEOF 32'hFFFFFFFF
`define D2_FILE_NAME "motorcycle_d2.d2"
`define LOG_FILE_NAME "SRAM_dump.log"

`define DP0_DUMP "dpRAM0_dump.log"
`define DP1_DUMP "dpRAM1_dump.log"

module tb_IDCT;

logic Clock_50;

wire [15:0] SRAM_data_io;
logic [15:0] SRAM_write_data, SRAM_read_data;
logic [17:0] SRAM_address_o;
logic [17:0] SRAM_address;
logic SRAM_we;
logic SRAM_UB_N;
logic SRAM_LB_N;
logic SRAM_WE_N;
logic SRAM_CE_N;
logic SRAM_OE_N;

logic resetn;
logic SRAM_ready;

logic start, finish;

// Unit under test
IDCT uut (
    .CLOCK_50_I(Clock_50),
    .resetn(resetn),
    .start(start),
    .SRAM_Read(SRAM_read_data),

    .SRAM_Write(SRAM_write_data),
    .SRAM_Addr(SRAM_address),
    .SRAM_we(SRAM_we),
    .finish(finish)
);

// The emulator for the external SRAM during simulation
tb_SRAM_Emulator SRAM_component (
    .Clock_50(Clock_50),
    .Resetn(resetn),
    
    .SRAM_data_io(SRAM_data_io),
    .SRAM_address(SRAM_address_o),
    .SRAM_UB_N(SRAM_UB_N),
    .SRAM_LB_N(SRAM_LB_N),
    .SRAM_WE_N(SRAM_WE_N),
    .SRAM_CE_N(SRAM_CE_N),
    .SRAM_OE_N(SRAM_OE_N)
);

// SRAM unit
SRAM_Controller SRAM_unit (
    .Clock_50(Clock_50),
    .Resetn(resetn),
    .SRAM_address(SRAM_address),
    .SRAM_write_data(SRAM_write_data),
    .SRAM_we_n(~SRAM_we),
    .SRAM_read_data(SRAM_read_data),        
    .SRAM_ready(SRAM_ready),
        
    // To the SRAM pins
    .SRAM_DATA_IO(SRAM_data_io),
    .SRAM_ADDRESS_O(SRAM_address_o),
    .SRAM_UB_N_O(SRAM_UB_N),
    .SRAM_LB_N_O(SRAM_LB_N),
    .SRAM_WE_N_O(SRAM_WE_N),
    .SRAM_CE_N_O(SRAM_CE_N),
    .SRAM_OE_N_O(SRAM_OE_N)
);

// Generate a 50 MHz clock
always begin
    # 10;
    Clock_50 = ~Clock_50;
end

// Task for filling the SRAM directly to shorten simulation time
task fill_SRAM;
    integer uart_file, file_data, temp, i, new_line_count;
    logic [15:0] buffer;
begin
    $write("Opening file \"%s\" for initializing SRAM\n\n", `D2_FILE_NAME);
    uart_file = $fopen(`D2_FILE_NAME, "rb");
    file_data = $fgetc(uart_file);
    i = 0;
    while (file_data != `FEOF) begin
        buffer[15:8] = file_data & 8'hFF;
        file_data = $fgetc(uart_file);          
        buffer[7:0] = file_data & 8'hFF;
        SRAM_component.SRAM_data[i] = buffer;
        i++;

        file_data = $fgetc(uart_file);
    end

    $fclose(uart_file);

    $write("Finish initializing SRAM\n\n");
end
endtask

// Task for dumping SRAM content
task SRAM_dump;
    integer output_file, i;
begin
    output_file = $fopen(`LOG_FILE_NAME, "wb");
    i = 0;
    while (i < 262144) begin
        $fwrite(output_file, "%x\n", SRAM_component.SRAM_data[i]);
        i++;
    end
    $fclose(output_file);
end
endtask

// Task for killing all V data in SRAM
task VClear;
    integer i;
begin
    i=127744;
    while (i < 146944) begin
        SRAM_component.SRAM_data[i]=16'b0;
        i++;
    end
end
endtask

// Task for generating master reset
task master_reset;
begin
    wait (Clock_50 !== 1'bx);
    @ (posedge Clock_50);
    $write("Applying global reset...\n\n");
    resetn = 1'b0;
    // Activate reset for 2 clock cycles
    @ (posedge Clock_50);
    @ (posedge Clock_50);   
    resetn = 1'b1;    
    $write("Removing global reset...\n\n"); 
end
endtask

// Task for dumping Dual Port RAM contents
//  Dumps Dual Port RAM 0 contents after first tCalc0
task dpRAM0_dump;
    integer i, dp0_file, dp0Data;
begin
    dp0_file = $fopen(`DP0_DUMP, "wb");
    i=0;
    while (i < 128) begin
        dp0Data = uut.dpRAM00.altsyncram_component.mem_data[i];
        $fwrite(dp0_file, "%d\t%x\n", i, dp0Data);
        i++;
    end
    $fclose(dp0_file);
end
endtask

// Task for dumping Dual Port RAM contents
//  Dumps Dual Port RAM 1 contents after first sCalc0
task dpRAM1_dump;
    integer i, dp1_file, dp1Data;
begin
    dp1_file = $fopen(`DP1_DUMP, "wb");
    i=0;
    while (i < 128) begin
        dp1Data = uut.dpRAM10.altsyncram_component.mem_data[i];
        $fwrite(dp1_file, "%d\t%x\n", i, dp1Data);
        i++;
    end
    $fclose(dp1_file);
end
endtask

// Initialize signals
initial begin
    // This is for setting the time format
    $timeformat(-3, 2, " ms", 10);
    
    $write("Simulation started at %t\n\n", $realtime);
    Clock_50 = 1'b0;
    
    // Apply master reset
    master_reset;
    
    @ (posedge Clock_50);
    @ (posedge Clock_50);

    fill_SRAM;
    $write("SRAM is now filled at %t\n\n", $realtime);

    start = 1'b1; 
    wait (uut.state != 0);
    $write("Starting IDCT at %t\n\n", $realtime);
    start = 1'b0;
    
    // Wait for tCalc0.finish to go to 1,
    //  start dumping dpRAM0
    wait(uut.tCalc0.finish == 1'b1 && uut.preIDCTcol == 5'd12 && uut.preIDCTrow == 5'd15);
    $write("Starting to dump dpRAM0");
    dpRAM0_dump;
    
    // Wait for sCalc0.finish to go to 1,
    //  start dumping dpRAM1
    wait(uut.sCalc0.finish == 1'b1 && uut.preIDCTcol == 5'd13 && uut.preIDCTrow == 5'd15);
    $write("Starting to dump dpRAM1");
    dpRAM1_dump;
    
    wait (uut.finish == 1);
    $write("IDCT finished at %t\n\n", $realtime);

    @ (posedge Clock_50);
    SRAM_dump;
    $stop;
end

endmodule
